/*
 * ============================================================================
 *
 *       Filename:  rctl.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  2014年09月28日 11时02分55秒
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  jianxi sun (jianxi), ycsunjane@gmail.com
 *   Organization:  
 *
 * ============================================================================
 */
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>

#include <sys/socket.h>
/* netdevice */
#include <sys/ioctl.h>
#include <net/if.h>
/* struct sockaddr_in */
#include <netinet/in.h>
/* inet_ntoa */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/* gethostbyname */
#include <netdb.h>
/* thread */
#include <pthread.h>
#include <sys/wait.h>
/* flock */
#include <sys/file.h>

#include "common.h"
#include "bash.h"
#include "rctl.h"
#include "arg.h"

static struct reg_t reg;

#define ISSERVER 	(0)
#define ISSUPER 	(1)

static char cmd[CMDLEN];
static char buf[BUFLEN];

static in_addr_t r_server(char ** array, int num)
{
	struct hostent *host;
	host = gethostbyname(array[num]);
	if(host != NULL && host->h_length > 0) {
		sys_debug("Try connect: %s\n", array[num]);
		return *(in_addr_t *)host->h_addr_list[0];
	}
	sys_warn("Can not get: %s\n", array[num]);
	return 0;
}

static int r_connect(struct sockaddr_in *addr,
	char **addrlist, unsigned int addrlen,
	int *portlist, unsigned int portlen)
{
	static int count = 0;
	sys_debug("Trying %d ......\n", count++);
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd < 0) {
		sys_warn("Create socket failed: %s(%d)\n", 
			strerror(errno), errno);
		return -1;
	}

	int i, j;
	in_addr_t server;
	for(i = 0; i < addrlen; i++) {
		socklen_t addr_len = sizeof(*addr);
		addr->sin_family = AF_INET;
		server = r_server(addrlist, i);
		if(!server) continue;
		addr->sin_addr.s_addr = server;

		for(j = 0; j < portlen; j++) {
			addr->sin_port = htons(portlist[j]);
			sys_debug("Try %s port: %d\n", 
				inet_ntoa(addr->sin_addr), portlist[j]);

			if(!connect(fd, (void *)addr, addr_len) && 
				tcp_alive(fd)) {
				sys_debug("connect success\n");
				return fd;
			}
			sys_debug("connect failed\n");
		}
	}
	close(fd);
	return -1;
}

static void ssl_shutdown(SSL *ssl)
{
	ssltcp_shutdown(ssl);
}

static void ssl_free(SSL *ssl)
{
	ssltcp_free(ssl);
}

static void getmac(unsigned char *dmac, char *nic)
{
	int sock;
	sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0) {
		sys_warn("Create dllayer socket failed: %s\n", 
			strerror(errno));
		exit(-1);
	}

	struct ifreq req;
	strncpy(req.ifr_name, nic, IFNAMSIZ-1);
	if(ioctl(sock, SIOCGIFHWADDR, &req) < 0) {
		sys_err("get %s mac failed: %s\n",
			nic, strerror(errno));
		exit(-1);
	}
	memcpy(dmac, req.ifr_hwaddr.sa_data, ETH_ALEN);
}

static int one_instance()
{
	int pid_file = open("/var/run/rctlcli.pid", 
		O_CREAT | O_RDWR, 0666);
	int rc = flock(pid_file, LOCK_EX | LOCK_NB);
	if(rc) {
		if(EWOULDBLOCK == errno) {
			sys_warn("another rctlcli is running\n");
			return -1;
		}
	}
	return 0;
}

static int rctlcmd(char *cmd, struct sockaddr_in *addr)
{
	if(!strcmp(cmd, RCTLBASH)) {
		bashfrom(addr);
		return 1;
	}

	return 0;
}

void *rctllink(void *arg)
{
	int type = (int)arg;
	char **addrlist;
	int *portlist;
	unsigned int addrlen, portlen;
	switch(type) {
	case ISSUPER:
		addrlist = args_info.super_arg;
		addrlen = args_info.super_given ? args_info.super_given : 1;
		portlist = args_info.port_arg;
		portlen = args_info.port_given ? args_info.port_given : 1;
		assert(addrlen > 0);
		break;
	case ISSERVER:
		addrlist = args_info.server_arg;
		addrlen = args_info.server_given;
		portlist = args_info.port_arg;
		portlen = args_info.port_given ? args_info.port_given : 1;
		if(addrlen == 0)  return NULL;
		break;
	default:
		sys_err("rctllink type is error: %d\n", type);
		panic();
	}

	int fd = -1;
	struct sockaddr_in addr;
reconnect:
	if(fd >= 0) close(fd);
	sleep(30);
	fd = r_connect(&addr, addrlist, addrlen, portlist, portlen); 
	if(fd < 0) goto reconnect;

	SSL *ssl = ssltcp_ssl(fd);
	if(!ssl) goto reconnect;

	if(ssltcp_connect(ssl) < 0) {
		ssltcp_free(ssl);
		goto reconnect;
	}

	int ret;
	ret = ssltcp_write(ssl, (char *)&reg, sizeof(reg));
	if(ret <= 0) {
		ssl_free(ssl);
		goto reconnect;
	}

	while(1) {
		/* max command len should less than
		 * CMDLEN, so ret always complete */
		ret = ssltcp_read(ssl, cmd, CMDLEN);
		if(ret <= 0) {
			ssl_free(ssl);
			goto reconnect;
		}
		cmd[ret] = 0;

		/* rctl system command */
		if(rctlcmd(cmd, &addr)) continue;

		strncat(cmd, " 2>&1", CMDLEN - strlen(cmd));
		FILE *fp; int size;
		fp = popen(cmd, "r"); 
		if(!fp) {
			sprintf(buf, "exec fail: %s\n", cmd);
			ret = ssltcp_write(ssl, buf, strlen(buf));
			if(ret <= 0) {
				ssl_free(ssl);
				goto reconnect;
			}
		} else {
			int isfirst = 1;
			do {
				size = fread(buf, 1, CMDLEN, fp);
				/* some command have no output */
				if(isfirst && size == 0) {
					sprintf(buf, "exec success: %s\n", cmd);
					ret = ssltcp_write(ssl, buf, strlen(buf));
					if(ret <= 0) {
						ssl_free(ssl);
						pclose(fp);
						goto reconnect;
					}
				}

				isfirst = 0;
				if(size > 0) {
					ret = ssltcp_write(ssl, buf, size);
					if(ret <= 0) {
						ssl_free(ssl);
						pclose(fp);
						goto reconnect;
					}
				}
			} while(size == BUFLEN);
			pclose(fp);
		}
	}
}

static void _rctl()
{
	ssltcp_init(0, NULL, NULL, args_info.ca_arg);

	/* prepare reg_t */
	strncpy(reg.class, args_info.class_arg, DEVID_LEN);
	getmac(reg.mac, args_info.wan_arg);

	pthread_t handle;
	pthread_create(&handle, NULL, rctllink, (void *)ISSUPER);
	pthread_create(&handle, NULL, rctllink, (void *)ISSERVER);
	pause();
}

static void proc_conffile(int argc, char *argv[])
{
	proc_args(argc, argv);
}

void rctl(int argc, char *argv[])
{
	if(one_instance() < 0)
		return;
	pid_t pid;
	if( (pid = fork()) < 0) {
		sys_warn("Fork failed: %s(%d)\n", 
			strerror(errno), errno);
		exit(-1);
	} else if(pid) {
		return;
	}

	proc_conffile(argc, argv);
	_rctl(args_info.class_arg, args_info.wan_arg);
	while(1) pause();
}

